package org.activequant.core.domainmodel.marketdepth;

import static org.activequant.util.tools.IdentityUtils.equalsTo;
import static org.activequant.util.tools.IdentityUtils.safeCompare;
import static org.activequant.util.tools.IdentityUtils.safeHashCode;

import java.util.LinkedList;
import java.util.List;

import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.data.MarketDataEntity;
/**
 * MarketDepth extends MarketDataEntity&lt;MarketDepth&gt;. 
 * Holds the following associated variables:
 * <ul>
 * <li>bidDepth(List&lt;MarketDepthRow&gt;)</li>
 * <li>askDepth(List&lt;MarketDepthRow&gt;)</li>
 * </ul>
 * Inherits the following variables:
 * <ul>
 * <li>id(Long)</li>
 * <li>instrumentSpecification(InstrumentSpecification)</li>
 * <li>timeStamp(TimeStamp)</li>
 * <li>receivedTimeStamp(TimeStamp)</li>
 * </ul>
 * @author Dimitar
 *
 */
public class MarketDepth extends MarketDataEntity<MarketDepth>{
	private static final long serialVersionUID = 8255084583085813761L;
	/**
	 * final static int OPERATION_INSERT = 0;
	 */
	final static int OPERATION_INSERT = 0;
	/**
	 * final static int OPERATION_UPDATE = 1;
	 */
    final static int OPERATION_UPDATE = 1;
    /**
     * final static int OPERATION_DELETE = 2;
     */
    final static int OPERATION_DELETE = 2;
    /**
     * public final static int SIDE_ASK = 0;
     */
    public final static int SIDE_ASK = 0;
    /**
     * public final static int SIDE_BID = 1;
     */
    public final static int SIDE_BID = 1;
    /**
     * public final static int MKT_DEPTH_DATA_RESET = 317;
     */
    public final static int MKT_DEPTH_DATA_RESET = 317;
    /**
     * public final static int DEPTH_SIZE=20;
     */
    public final static int DEPTH_SIZE=20;
	/**
	 * private List&lt;MarketDepthRow&gt;  bidDepth = new LinkedList&lt;MarketDepthRow&gt;();
	 */
	private List<MarketDepthRow>  bidDepth = new LinkedList<MarketDepthRow>();
	/**
	 * private List&lt;MarketDepthRow&gt;  askDepth = new LinkedList&lt;MarketDepthRow&gt;();
	 */
	private List<MarketDepthRow>  askDepth = new LinkedList<MarketDepthRow>();
	/**
	 * An empty MarketDepth(extends MarketDataEntity&lt;MarketDepth&gt;) constructor
	 */
	public MarketDepth(){
	}
	/**
	 * Constructs a MarketDepth(extends MarketDataEntity&lt;MarketDepth&gt;) using the given instrumentSpecification(InstrumentSpecification) to set its inherited instrumentSpecification(InstrumentSpecification)
	 * @param instrumentSpecification
	 */
	public MarketDepth(InstrumentSpecification instrumentSpecification){
		this.setInstrumentSpecification(instrumentSpecification);
	}
	/**
	 * Creates a MarketDepthRow with the given marketMaker(String), price(double) and size(int) and adds it to the given side(int) and position(int).<br/>
	 * The given side(int) can be SIDE_BID(1) or SIDE_ASK(0).<br/>
	 * The given position(side) is a 0-based index.	Any subsequent MarketDepthRows are shifted to the left (subtracts one from their indices).
	 * @param side
	 * @param position
	 * @param marketMaker
	 * @param price
	 * @param size
	 */
	public void addOrderAt(int side, int position, String marketMaker, double price, int size)
    {
		MarketDepthRow newData = new MarketDepthRow(marketMaker, price, size);
		if(side==SIDE_BID){
			synchronized(bidDepth){
				while(bidDepth.size()<position){
					MarketDepthRow dummy = new MarketDepthRow("", 0d, 0);
					bidDepth.add(dummy);
				}
				bidDepth.add(position, newData);
				if(bidDepth.size()>DEPTH_SIZE){
					bidDepth.remove(bidDepth.size()-1);
		        }
			}
        //fireTableRowsInserted(position, position);
		}else{
			synchronized(askDepth){
				while(askDepth.size()<position){
					MarketDepthRow dummy = new MarketDepthRow("", 0d, 0);
					askDepth.add(dummy);
				}
				askDepth.add(position, newData);
				if(askDepth.size()>DEPTH_SIZE){
					askDepth.remove(askDepth.size()-1);
		        }
			}
		}
    }
	/**
	 * Removes the MarketDepthRow found at the given side(int) and position(side). The given side(int) can be SIDE_BID(1) or SIDE_ASK(0).<br/>
	 * The given position(side) is a 0-based index.	Any subsequent MarketDepthRows are shifted to the left (subtracts one from their indices)
	 * @param side
	 * @param position
	 */
	public void removeOrderAt(int side, int position)
    {
		if(side==SIDE_BID){
			synchronized(bidDepth){
				if(bidDepth.size()>position)
				{bidDepth.remove(position);}
			}
        //fireTableRowsDeleted(position, position);
		}else{
			synchronized(askDepth){
				if(askDepth.size()>position)
				{askDepth.remove(position);}
			}
		}
    }
	/**
	 * Returns the MarketDepthRow found at the given side(int) and orderPosition(side). The given side(int) can be SIDE_BID(1) or SIDE_ASK(0).
	 * The given orderPosition(side) is a 0-based index.
	 * @param side
	 * @param orderPosition
	 * @return
	 */
	public MarketDepthRow getOrderAt(int side, int orderPosition) {		
		if(side==SIDE_BID){
			synchronized(bidDepth){
				while(bidDepth.size()<=orderPosition){
					MarketDepthRow dummy = new MarketDepthRow("", 0d, 0);
					try {
						bidDepth.add(dummy);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				return bidDepth.listIterator(orderPosition).next();
			}
        //fireTableRowsDeleted(position, position);
		}else{
			synchronized(askDepth){
				while(askDepth.size()<=orderPosition){
					MarketDepthRow dummy = new MarketDepthRow("", 0d, 0);
					try {
						askDepth.add(dummy);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				return askDepth.listIterator(orderPosition).next();
			}
		}
    }
	
    public void updateCumSizesAndAvgPrices(int side,int baseRow)
    {
    	if(side==SIDE_BID && bidDepth.size()>baseRow){
    		synchronized(bidDepth){
		        int cumSize = 0;
		        double totalPrice = 0.0;
		        MarketDepthRow	tmpRow = null;
		        if (baseRow > 0) {
		            tmpRow = (MarketDepthRow)bidDepth.get(baseRow - 1);
		            cumSize = tmpRow.getCumSize();
		            totalPrice = tmpRow.getPrice() * cumSize;
		        }
		        for (int ctr = baseRow ; ctr < bidDepth.size() ; ctr++)
		        {
		            tmpRow = (MarketDepthRow) bidDepth.get(ctr);
		            cumSize += tmpRow.getSize();
		            totalPrice += (tmpRow.getPrice() * tmpRow.getSize());
		            tmpRow.setCumSize(cumSize);
		            tmpRow.setAvgPrice(totalPrice / cumSize);
		            //fireTableCellUpdated(ctr, 3);
		            //fireTableCellUpdated(ctr, 4);
		        }
    		}
    	}else if(askDepth.size()>baseRow){
			synchronized(askDepth){
				int     cumSize = 0;
		        double  totalPrice = 0.0;
		        MarketDepthRow	tmpRow = null;

		        if (baseRow > 0) {
		            tmpRow = (MarketDepthRow)askDepth.get(baseRow - 1);
		            cumSize = tmpRow.getCumSize();
		            totalPrice = tmpRow.getPrice() * cumSize;
		        }
		        for (int ctr = baseRow ; ctr < askDepth.size() ; ctr++)
		        {
		            tmpRow = (MarketDepthRow) askDepth.get(ctr);
		            cumSize += tmpRow.getSize();
		            totalPrice += (tmpRow.getPrice() * tmpRow.getSize());
		            tmpRow.setCumSize(cumSize);
		            tmpRow.setAvgPrice(totalPrice / cumSize);
		            //fireTableCellUpdated(ctr, 3);
		            //fireTableCellUpdated(ctr, 4);
		        }
			}
		}
    	
//    	if(bidDepth.size()>20||askDepth.size()>20){
//	    	System.out.println("here");
//	    }
    }
    /**
     * Clears the associated bidDepth(List&lt;MarketDepthRow&gt;) and askDepth(List&lt;MarketDepthRow&gt;)
     */
    synchronized public void reset() {
    	bidDepth.clear();
    	askDepth.clear();
        //fireTableDataChanged();
    }
	
    public void updateMktDepth(int position, String marketMaker, int operation, int side, double price, int size) {
    	try{
    		if(operation==MKT_DEPTH_DATA_RESET){
    			reset();
    			return;
    		}
    		MarketDepthRow tmpRow = null;

            if (operation == OPERATION_INSERT )
            {
            	addOrderAt(side, position, marketMaker, price, size);
            }
            else if (operation == OPERATION_UPDATE )
            {	
            	tmpRow = getOrderAt(side, position);
            	if ( tmpRow != null ) {
                    tmpRow.setPrice(price);
                    tmpRow.setSize(size);
            	}
//                if ( side == SIDE_BID ) {
//                     m_bidModel.fireTableRowsUpdated(position, position);
//                }else {                            
//                     m_askModel.fireTableRowsUpdated(position, position);
//                }
            }
            else if  (operation == OPERATION_DELETE)
            {
            	removeOrderAt(side,position);                   
            }
            
            updateCumSizesAndAvgPrices(side,position);            
    	}
        catch( Exception e) {
            System.out.println("Exception: " + e.getMessage());
            e.printStackTrace();
        }
    }
    @Override
	public MarketDepth clone() {
    	MarketDepth clone=new MarketDepth(this.getInstrumentSpecification());
		clone.bidDepth.addAll(this.bidDepth);
		clone.askDepth.addAll(this.askDepth);
		return clone;
	}
	

    @Override
	public String toString() {
    	StringBuffer result=new StringBuffer();
    	InstrumentSpecification spec=this.getInstrumentSpecification();
    	result.append(" depth: "+spec.getSymbol().toString()+" "+spec.getSecurityType().toString()+"@"+spec.getExchange()+" timeStamp: "+this.getTimeStamp()+"\n");
    	result.append("Ask:\n");
    	for(MarketDepthRow askRow:askDepth){
    		result.append(askRow.toString());
    		result.append('\n');
		}
    	result.append("Bid:\n");
    	for(MarketDepthRow bidRow:bidDepth){
    		result.append(bidRow.toString());
    		result.append('\n');
		}    	
		return result.toString();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int hashCode() {
		// ATTENTION: keep in sync with compareTo();
		return safeHashCode(this.getInstrumentSpecification()) + safeHashCode(this.getTimeStamp());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean equals(Object other) {
		// NOTE: delegates to compareTo()
		return equalsTo(this, other);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public int compareTo(MarketDepth other) {
		// ATTENTION: keep in sync with hashCode();
		int rc;
		rc = safeCompare(this.getInstrumentSpecification(), other.getInstrumentSpecification());
		if (rc != 0) return rc;
		rc = safeCompare(this.getTimeStamp(), other.getTimeStamp());
		if (rc != 0) return rc;
		return rc;
	}
	/**
	 * returns the associated bidDepth(List&lt;MarketDepthRow&gt;)
	 * @return
	 */
	public List<MarketDepthRow> getBidDepth() {
		return bidDepth;
	}
	/**
	 * returns the associated askDepth(List&lt;MarketDepthRow&gt;)
	 * @return
	 */
	public List<MarketDepthRow> getAskDepth() {
		return askDepth;
	}
}
